//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.RuntimeServices.WorkflowOutput
{
    using System;
    using System.Collections;
    using System.Drawing;
    using System.Globalization;
    using System.Text.RegularExpressions;
    using System.Workflow.Runtime.Hosting;
    using System.Workflow.Runtime.Tracking;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.DataLayer;
    using Microsoft.Research.eResearch.Blackboard.Common;
    using Microsoft.Research.eResearch.Common;
    using Microsoft.Research.eResearch.RuntimeServices.Blackboard;
    using Microsoft.Research.eResearch.RuntimeServices.Common;
    using Microsoft.Research.eResearch.RuntimeServices.Ontology;
    using TridentContext;
    using TridentAPI;

    /// <summary>
    /// This class contains the functionality to save the workflow outputs.
    /// </summary>
    public class WorkflowOutputSaveService : WorkflowRuntimeService
    {
        /// <summary>
        /// Service Provider used to retrieve a service object.
        /// </summary>
        private IServiceProvider serviceProvider;

        /// <summary>
        /// Registry connection object.
        /// </summary>
        private Connection registryConnection;

        /// <summary>
        /// Stores the Iterator Qualified Names and their current index values.
        /// </summary>
        private Hashtable iteratorHash = new Hashtable();

        /// <summary>
        /// Saves the workflow required outputs to the registry.
        /// </summary>
        /// <param name="serviceProvider">The service provider.</param>
        /// <param name="input">Tracking record object.</param>
        public void SaveWorkflowOutputs(IServiceProvider serviceProvider, object input)
        {
            this.serviceProvider = serviceProvider;

            ExceptionHandler.Handle(
                PolicyName.LoggingRethrowPolicy,
                () =>
                {
                    if (input is ActivityTrackingRecord)
                    {
                        WriteActivityTrackingRecord((ActivityTrackingRecord)input);
                    }
                    if (input is UserTrackingRecord)
                    {
                        HandleUserTrackingRecord((UserTrackingRecord)input);
                    }
                });
        }

        /// <summary>
        /// Called for Service start logic.
        /// </summary>
        protected override void Start()
        {
            base.Start();
        }

        /// <summary>
        /// Called when the service is being started.
        /// </summary>
        protected override void OnStarted()
        {
            base.OnStarted();
        }

        /// <summary>
        /// Called for the service stop logic.
        /// </summary>
        protected override void Stop()
        {
            base.Stop();
        }

        /// <summary>
        /// Called when the service is being stopped.
        /// </summary>
        protected override void OnStopped()
        {
            base.OnStopped();
        }

        /// <summary>
        /// Contains the logic to retrieve and store the current index value for the iterator
        /// specified in the user tracking record.
        /// </summary>
        /// <param name="userTrackingRecord"></param>
        private void HandleUserTrackingRecord(UserTrackingRecord userTrackingRecord)
        {
            if (userTrackingRecord.UserDataKey == "IterationValue")
            {
                string key = userTrackingRecord.QualifiedName;
                iteratorHash[key] = userTrackingRecord.UserData;
            }
        }

        /// <summary>
        /// Contains the logic to save the output to the registry if the activity tracking record
        /// corresponds to one of the required workflow output parameters.        
        /// </summary>
        /// <param name="activityTrackingRecord">The activity tracking record object.</param>
        private void WriteActivityTrackingRecord(ActivityTrackingRecord activityTrackingRecord)
        {

            // Retrieve the OutputParams HashTable from the JobExecutionService
            Hashtable outputParamsHash = null;
            JobExecutionService executionService = serviceProvider.GetJobExecutionService();
            if (executionService != null)
            {
                outputParamsHash = executionService.OutputParamsHash;
            }

            ExceptionHandler.Handle(
                PolicyName.LoggingRethrowPolicy,
                () =>
                {
                    // Retrieve the connection object using the Context object.
                    Context ctx = Context.Get();
                    this.registryConnection = ctx.Connection.Clone();
                    this.registryConnection.Open();

                    ExternalRegistryHelperProxy.Initialize(this.registryConnection);

                    foreach (TrackingDataItem item in activityTrackingRecord.Body)
                    {
                        ExceptionHandler.Handle(
                                   PolicyName.LoggingRethrowPolicy,
                                   () =>
                                   {
                                       string paramKey = WorkflowRuntimeHelper.FetchActivityParamUniqueKey(activityTrackingRecord.QualifiedName, item.FieldName);
                                       if (outputParamsHash != null && outputParamsHash.ContainsKey(paramKey))
                                       {
                                           ParameterAssignment param = (ParameterAssignment)outputParamsHash[paramKey];
                                           string iteratorKey = WorkflowRuntimeHelper.FetchActivityIteratorUniqueKey(paramKey);
                                           DataProduct dataproduct = DataProduct.Create(GetLabel(param.OutputLabel, outputParamsHash, iteratorKey), param.Type, this.registryConnection);
                                           dataproduct.ProducedBy = ctx.WFInstance;
                                           dataproduct.CreatorActivity = param.Instance;
                                           dataproduct.CreatorParameter = param;
                                           GetDataProductValue(ref dataproduct, param, item.Data);
                                           SendMessage(activityTrackingRecord, dataproduct);
                                       }
                                   });
                    }
                },

            () =>
            {
                this.CloseRegistryConnection();
            });
        }

        /// <summary>
        /// Sends the message.
        /// </summary>
        /// <param name="record">Activity Tracking Record.</param>
        /// <param name="dataProduct">The data product.</param>
        private void SendMessage(ActivityTrackingRecord record, DataProduct dataProduct)
        {
            OntologyMappingService mapper = null;
            IBlackboardService blackboard = null;

            serviceProvider.TryGetOntologyMappingService(out mapper);
            serviceProvider.TryGetBlackboardService(out blackboard);

            if (blackboard != null && mapper != null)
            {
                JobExecutionService jobIdService = serviceProvider.GetJobExecutionService();
                BlackboardMessage message = new BlackboardMessage();
                message.Add("trident:MachineName", System.Environment.MachineName);
                if (record is ActivityTrackingRecord)
                {
                    ActivityTrackingRecord rec = record as ActivityTrackingRecord;
                    message.Add("trident:ActivityType", rec.ActivityType.ToString());
                }
                message.Add(jobIdService.JobIdConcept.Concept, jobIdService.JobIdConcept.Value);

                message.Add("trident:OutputDataProductID", ((IObject)dataProduct).ID.ToString());

                blackboard.PublishMessage(message);
            }
        }


        /// <summary>
        /// Saves the value of the data product in the registry depending on the output type
        /// specified.
        /// </summary>
        /// <param name="dataProduct">The DataProduct object.</param>
        /// <param name="param">The ParameterAssignment object.</param>
        /// <param name="dataProductValue">The DataProduct value.</param>
        private void GetDataProductValue(ref DataProduct dataProduct, ParameterAssignment param, object dataProductValue)
        {
            string dataProductType = param.Type;
            if ((0 == dataProductType.ToLower().CompareTo("text/richtext")) ||
                   (0 == dataProductType.ToLower().CompareTo("text/plain")) ||
                   (0 == dataProductType.ToLower().CompareTo("text/html")) ||
                   (0 == dataProductType.ToLower().CompareTo("text/xml")))
            {
                if (string.IsNullOrEmpty(dataProductValue.ToString()))
                {
                    throw new ArgumentNullException("dataProductValue", "StringValue input is null or empty");
                }

                dataProduct.ContentsAsString = FetchSerializedString(param, dataProductValue);
            }
            else if ((0 == dataProduct.Type.ToLower().CompareTo("image/gif"))
                    || (0 == dataProduct.Type.ToLower().CompareTo("image/jpg"))
                    || (0 == dataProduct.Type.ToLower().CompareTo("image/bmp"))
                    || (0 == dataProduct.Type.ToLower().CompareTo("image/png")))
            {
                if (dataProductValue == null)
                {
                    throw new ArgumentNullException("dataProductValue", "ImageValue input is null");
                }

                dataProduct.ContentsAsImage = (Image)dataProductValue;
            }
            else
            {
                if (dataProductValue == null)
                {
                    throw new ArgumentNullException("dataProductValue", "ByteArrayValue input is null");
                }

                dataProduct.ContentAsByteArray = (byte[])dataProductValue;
            }
        }

        /// <summary>
        /// Fetches the serialized string to be saved to the registry.
        /// </summary>
        /// <param name="param">The Parameter assignment object.</param>
        /// <param name="dataProductValue">The value of the workflow required output.</param>
        /// <returns>The serialized string.</returns>
        private string FetchSerializedString(ParameterAssignment param, object dataProductValue)
        {
            //Check whether the registry connection is valid
            if (null == registryConnection || !registryConnection.Alive)
            {
                return null;
            }
            string actualVal = string.Empty;

            // First try to serialize with TypeInitializer.
            TypeProviderHelper.Initialize(registryConnection);
            ExternalRegistryHelperProxy.Initialize(registryConnection);
            TypeInitializer typeInit = null;

            ExceptionHandler.Handle(
                PolicyName.LoggingPolicy,
                () =>
                {
                    typeInit = TypeProviderHelper.FetchTypeInitializer(param.ActivityParameter.Type, Microsoft.Research.DataLayer.Runtime.WPF);
                });

            if (typeInit != null)
            {
                ExceptionHandler.Handle(
                PolicyName.LoggingPolicy,
                () =>
                {
                    BaseTypeInitializer baseTypeInit = TypeProviderHelper.LoadInitializer(typeInit, string.Empty);
                    if (baseTypeInit != null)
                    {
                        actualVal = baseTypeInit.GetSerializedVersion(dataProductValue);
                    }
                });
            }
            else
            {
                ExceptionHandler.Handle(
                PolicyName.LoggingPolicy,
                () =>
                {
                    actualVal = TypeProviderHelper.FetchSerializedValue(AssemblyLoad.SearchTypeIncurrentDomain(param.ActivityParameter.Type), dataProductValue);
                });

                if (null != dataProductValue && string.IsNullOrEmpty(actualVal))
                {
                    actualVal = dataProductValue.ToString();
                }
            }

            return actualVal;

        }

        /// <summary>
        /// Fetches the output label for the workflow output parameter after replacing the wild
        /// cards.
        /// </summary>
        /// <param name="outputLabel">Output label specified by the user.</param>
        /// <param name="outputLabel">Output parameters hash table.</param>
        /// <param name="iteratorKey">Iteration key to determine the iterator value.</param>
        /// <returns>Output label that will be saved in the registry.</returns>
        private string GetLabel(string outputLabel, Hashtable outputParamsHash, string iteratorKey)
        {
            string label = outputLabel;
            label = Regex.Replace(label, "([%][Gg][Uu][Ii][Dd][%])", System.Guid.NewGuid().ToString());
            label = Regex.Replace(label, "([%][Dd][Aa][Tt][Ee][Tt][Ii][Mm][Ee][.][Nn][Oo][Ww][%])", DateTime.Now.ToString(CultureInfo.CurrentUICulture));

            if (label.ToUpper(CultureInfo.InvariantCulture).Contains("%ITERATOR%") && outputParamsHash.ContainsKey(iteratorKey))
            {
                string iteratorQualifiedName = outputParamsHash[iteratorKey].ToString();
                if (iteratorHash.ContainsKey(iteratorQualifiedName))
                {
                    label = Regex.Replace(label, "([%][Ii][Tt][Ee][Rr][Aa][Tt][Oo][Rr][%])", iteratorHash[iteratorQualifiedName].ToString());
                }
            }
            return label;
        }

        /// <summary>
        /// Closes the registry connection.
        /// </summary>
        private void CloseRegistryConnection()
        {
            ExceptionHandler.Handle(
            "Logging Policy",
            () =>
            {
                // Close the registry Connection.
                if (registryConnection != null)
                {
                    registryConnection.Close();
                    registryConnection = null;
                }
            });
        }
    }
}
